package org.thanos.demo.config;


import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.*;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.CollectionUtils;
import org.thanos.demo.service.TaskEntity;
import org.thanos.demo.service.TaskService;
import org.thanos.demo.service.TaskType;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import static org.thanos.demo.constants.DynamicTaskConstants.PERIODIC_TRIGGER_PERIOD;

/**
 * @author H412447
 * @since 11/18/2021
 */
@Slf4j
@Configuration
public class DynamicTaskConfigurer implements SchedulingConfigurer {

    private final ConcurrentHashMap<Integer, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Integer, Task> tasks = new ConcurrentHashMap<>();
    private List<TaskEntity> dynamicTasks = new ArrayList<>();
    private ScheduledTaskRegistrar registrar;

    @Resource
    private TaskService taskService;

    public void initTask() {
        List<TaskEntity> enableTasks = taskService.allEnableTasks();
        if (!CollectionUtils.isEmpty(enableTasks)) {
            dynamicTasks = new ArrayList<>(enableTasks);
        }
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar) {
        this.registrar = registrar;

        this.registrar.addTriggerTask(() -> {
                    if (!CollectionUtils.isEmpty(dynamicTasks)) {
                        log.info("检测动态定时任务列表...当前任务数量为：{}", dynamicTasks.size());
                        refreshTasks();
                    }
                }
                , triggerContext
                        -> new PeriodicTrigger(Duration.of(PERIODIC_TRIGGER_PERIOD, ChronoUnit.SECONDS))
                        .nextExecution(triggerContext));
    }

    private boolean exists(List<TaskEntity> tasks, Integer taskId) {
        for (TaskEntity task : tasks) {
            if (task.getId().equals(taskId)) {
                return true;
            }
        }
        return false;
    }


    public List<TaskEntity> getTaskConstants() {
        return dynamicTasks;
    }

    @PreDestroy
    public void destroy() {
        this.registrar.destroy();
    }

    public void addTaskToQueue(TaskEntity task) {
        this.getTaskConstants().add(task);
        this.refreshTasks();
    }

    public void removeTaskFromQueue(Integer taskId) {
        Optional<TaskEntity> taskOptional = getTaskConstants().stream()
                .filter(t -> Objects.equals(t.getId(), taskId)).findFirst();
        taskOptional.ifPresent(dynamicTasks::remove);
        refreshTasks();
    }

    private DynamicTask buildTimingTask(TaskEntity task) {
        DynamicTask timingTask = new DynamicTask();
        BeanUtils.copyProperties(task, timingTask);
        timingTask.setTaskName(task.getId());
        return timingTask;
    }

    private void refreshTasks() {
        if (CollectionUtils.isEmpty(dynamicTasks)) {
            scheduledFutures.forEach((t, f) -> f.cancel(false));
            scheduledFutures.clear();
            tasks.clear();
        } else if (!CollectionUtils.isEmpty(dynamicTasks)) {
            Set<Integer> taskIds = scheduledFutures.keySet();
            for (Integer taskId : taskIds) {
                if (!exists(dynamicTasks, taskId)) {
                    scheduledFutures.get(taskId).cancel(false);
                    scheduledFutures.remove(taskId);
                    tasks.remove(taskId);
                }
            }
            dynamicTasks.forEach(taskEntity -> {
                if (tasks.containsKey(taskEntity.getId())) {
                    return;
                }
                DynamicTask task = this.buildTimingTask(taskEntity);
                TaskType type = TaskType.idOf(taskEntity.getType());
                switch (type) {
                    case CRON -> addCronTask(task);
                    case FIXED_RATE -> addFixedRateTask(task);
                    case FIXED_DELAY -> addFixedDelayTask(task);
                    default -> {
                    }
                }
            });
        }
    }

    private void addCronTask(DynamicTask dynamicTask) {
        CronTask cronTask = new CronTask(dynamicTask, dynamicTask.getCron());
        ScheduledFuture<?> cronFuture = Objects.requireNonNull(this.registrar.getScheduler())
                .schedule(cronTask.getRunnable(), cronTask.getTrigger());
        tasks.put(dynamicTask.getId(), cronTask);
        scheduledFutures.put(dynamicTask.getId(), cronFuture);
    }

    private void addFixedRateTask(DynamicTask dynamicTask) {
        FixedRateTask rateTask = new FixedRateTask(dynamicTask, dynamicTask.getInterval(),
                dynamicTask.getInitialDelay());
        ScheduledFuture<?> rateFuture = Objects.requireNonNull(this.registrar.getScheduler())
                .scheduleAtFixedRate(rateTask.getRunnable(), rateTask.getInterval());
        tasks.put(dynamicTask.getId(), rateTask);
        scheduledFutures.put(dynamicTask.getId(), rateFuture);
    }

    private void addFixedDelayTask(DynamicTask dynamicTask) {
        FixedDelayTask delayTask = new FixedDelayTask(dynamicTask, dynamicTask.getInterval(),
                dynamicTask.getInitialDelay());
        ScheduledFuture<?> delayFuture = Objects.requireNonNull(this.registrar.getScheduler())
                .scheduleWithFixedDelay(delayTask.getRunnable(), delayTask.getInterval());
        tasks.put(dynamicTask.getId(), delayTask);
        scheduledFutures.put(dynamicTask.getId(), delayFuture);
    }
}
